﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Collections;
using System.Web.UI;

namespace Evolution.UI.WebParts
{
    [Serializable]
    public class Condition : ISerializable, IEnumerable<AssertionElement>
    {
        #region Fields

        private List<AssertionElement> _list;

        #endregion

        #region Constructors

        public Condition()
        {
            this._list = null;
        }

        protected Condition(SerializationInfo info, StreamingContext context)
        {
            if (info == null)
                throw new System.ArgumentNullException("info");

            int count = info.GetInt32("Count");
            for (int i = 0; i < count; i++)
            {
                AssertionElement c = (AssertionElement)info.GetValue("AssertionElement" + i, typeof(AssertionElement));
                c.Parent = this;
                this.List.Add(c);
            }
        }

        #endregion

        #region Properties and indexers

        public int Count
        {
            get { return this.List.Count; }
        }

        internal List<AssertionElement> List
        {
            get
            {
                if (this._list == null)
                    this._list = new List<AssertionElement>();

                return this._list;
            }
        }

        public BaseAssertion[] Assertions
        {
            get
            {
                if (this.List.Count == 0)
                    return new BaseAssertion[] { };

                AssertionElement[] connector = (AssertionElement[])Array.CreateInstance(typeof(AssertionElement), this.List.Count);
                this.List.CopyTo(connector);

                return (from a in connector select a.Assertion).ToArray<BaseAssertion>();
            }
        }

        public BooleanOperators[] Operators
        {
            get
            {
                if (this.List.Count == 0)
                    return new BooleanOperators[] { };

                AssertionElement[] connector = (AssertionElement[])Array.CreateInstance(typeof(AssertionElement), this.List.Count);
                this.List.CopyTo(connector);

                return (from a in connector select a.Operator).ToArray<BooleanOperators>();
            }
        }

        public AssertionElement[] AssertionElements
        {
            get
            {
                if (this.List.Count == 0)
                    return new AssertionElement[] { };

                AssertionElement[] connector = (AssertionElement[])Array.CreateInstance(typeof(AssertionElement), this.List.Count);
                this.List.CopyTo(connector);

                return connector;
            }
        }

        public AssertionElement this[int index]
        {
            get
            {
                AssertionElement conn = null;

                try { conn = this.List[index]; }
                catch { }

                return conn;
            }
        }

        #endregion

        #region Methods

        public static bool Evaluate(Condition condition, Page page, PageActionWebPart part)
        {
            if (page == null)
                throw new ArgumentNullException("page");

            if (part == null)
                throw new ArgumentNullException("part");

            if (condition == null)
                throw new ArgumentNullException("condition");

            bool result = true;

            foreach (AssertionElement ae in condition.List)
            {
                bool localResult = true;

                if (ae.Operator == BooleanOperators.And && !result ||
                    ae.Operator == BooleanOperators.Or && result)
                    break;

                localResult = ae.Assertion.Evaluate(page, part);

                switch (ae.Operator)
                {
                    case BooleanOperators.None:
                        result = localResult;
                        break;
                    case BooleanOperators.And:
                        result &= localResult;
                        break;
                    case BooleanOperators.Or:
                        result |= localResult;
                        break;
                }
            }
            return result;
        }

        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            if (info == null)
                throw new System.ArgumentNullException("info");

            int count = this._list != null ? this._list.Count : 0;
            info.AddValue("Count", count);
            for(int i = 0; i < count; i++)
                info.AddValue("AssertionElement" + i, this._list[i]);
        }

        public AssertionElement Add(BaseAssertion assertion)
        {
            if (this.List.Count != 0)
                throw new InvalidOperationException("The AssertionList is not empty. Use AddFirst method to add the assertion as the first item.");

            if (assertion == null)
                throw new ArgumentNullException("assertion");

            AssertionElement c = new AssertionElement(this, assertion);
            this.List.Add(c);
            return c;
        }

        public AssertionElement Add(BooleanOperators op, BaseAssertion assertion)
        {
            if (op == BooleanOperators.None)
                throw new ArgumentOutOfRangeException("op", "Boolean operator can not be None.");

            if (assertion == null)
                throw new ArgumentNullException("assertion");

            AssertionElement c = new AssertionElement(this, this.List.Count == 0 ? BooleanOperators.None : op, assertion);
            this.List.Add(c);

            return c;
        }

        public AssertionElement AddFirst(BaseAssertion assertion, BooleanOperators op)
        {
            AssertionElement c = new AssertionElement(this, assertion);
            this.List.Insert(0, c);
            if (this.List.Count > 1)
                this.List[1].Operator = op;

            return c;
        }

        public void AddRange(BaseAssertion[] assertions, BooleanOperators[] operators)
        {
            int i = -1;
            foreach (BaseAssertion assertion in assertions)
            {
                if (i++ == 0)
                    this.Add(assertion);
                else
                    this.Add(operators[i++], assertion);
            }
        }

        public AssertionElement InsertAt(int index, BaseAssertion assertion, BooleanOperators op)
        {
            AssertionElement c;
            if (this.List.Count == 0)
                c = this.Add(assertion);
            else if (index == 0)
                c = this.AddFirst(assertion, op);
            else
                this.List.Insert(index, c = new AssertionElement(this, op, assertion));

            return c;
        }

        public int IndexOf(AssertionElement r)
        {
            return this.List.IndexOf(r);
        }

        public AssertionElement Remove(int index)
        {
            AssertionElement c = this.List[index];
            this.List.Remove(c);

            if (index == 0 && this.List.Count > 0)
                this.List[0]._operator = BooleanOperators.None;

            c.Parent = null;
            return c;
        }

        public AssertionElement Remove(AssertionElement c)
        {
            this.List.Remove(c);
            c.Parent = null;
            return c;
        }

        public void Clear()
        {
            this.List.Clear();
        }

        public IEnumerator<AssertionElement> GetEnumerator()
        {
            foreach (AssertionElement c in this.List)
                yield return c;
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            foreach (AssertionElement c in this.List)
                yield return c;
        }

        #endregion
    }
}
